local ngx = ngx
local require = require;
local ngx     = ngx;
local pairs   = pairs;
local string  = string;
local collectgarbage = collectgarbage;
local table = table;
local io = io;
local assert = assert;
local tonumber = tonumber;
local ipairs = ipairs;
local setmetatable = setmetatable;
local tostring = tostring
local type = type

local env = require('modules.env.env')


--require for commhm
local uu = require("modules.commhm.utils")
local log = require("modules.commhm.log")
local ns_time = require("modules.commhm.time")
local ns_network = require("modules.commhm.network")
local mmh3 = require("modules.commhm.resty.murmurhash3")
local murmurhash3 = mmh3.murmurhash3
local murmurhash3_128 = mmh3.murmurhash3


--------------------------------------------------------------
-- ABTest SKD
-- author：kenner

--------------------------------------------------------------

local AB_TEST_TJ_RATE = 1000

local FOO = {
    speed = 42,
    ab_test_tj = {},
    server_config_ab_test = {},
    server_config_ab_id_list = {},
    ab_id_to_key = {},
    ab_id_to_type = {},
    ab_test_config = {},
    blank_guid_config = {},
}

-- content_: string
function FOO.LoadABTestConfig( content_ )
    local config_ = uu.json2table( content_ )
    if  config_ then
        local t_key_for_data_ = {}
        for _, sec in ipairs(config_) do
            if sec.trafic_expire then
                local exp_time_ = uu.get_time_stamp( sec.trafic_expire )
                if  exp_time_ > uu.now() then
                    local key = tonumber( sec.exp_id )
                    sec.trafic_expire = exp_time_   --把时间转换成秒

                    ---blank_guid 白名单设置成一个dict
                    if  sec.blank_guid then
                        if type(sec.blank_guid) == 'string' then
                            local t_ = uu.split( sec.blank_guid, ',' )
                            for i=1, #t_ do
                                if  #t_[i] > 0 and t_[i] ~= "" then
                                    --解决一个层因为加了白名单，导致多个实验命中的BUG，把白名单放到一个公共变量里
                                    local blank_key = ''..sec.trafic_domain.id..sec.trafic_scene.id..sec.logic_layer.id..t_[i]
                                    FOO.blank_guid_config[ blank_key ] = sec.exp_id
                                end
                            end
                        end
                    end
                    t_key_for_data_[ key ] = sec
                end
            end
        end
        log.day_list_simple( "sdk_abtest", "ab_test_config", #t_key_for_data_ )
        log.day_list_simple( "sdk_abtest", "blank_guid", uu.to_str(FOO.blank_guid_config) )
        FOO.ab_test_config = t_key_for_data_
        return t_key_for_data_
    else
        return nil
    end
end

--格式化开关配置，提供外部调用
-- switchConfig_:json
function FOO.LoadABTestSwitchConfig( switchConfig_ )
    local tmp_exp_config_ = {}
    for k, v in pairs( switchConfig_ ) do
        if  type( v )=='table' then
            FOO.server_config_ab_test[ k ] = { exp = nil, compare = nil }
            if  v.exp then
                for kk, vv in pairs( v.exp ) do
                    if  type( vv )=='table' and vv.on then
                        FOO.server_config_ab_test[ k ].exp = kk
                        FOO.server_config_ab_id_list[ kk ] = vv.on
                        tmp_exp_config_[ k .. "_" .. kk ] = vv.on    --- exp_name999
                        FOO.ab_id_to_key[ kk ] = k
                        FOO.ab_id_to_type[ kk ] = 'exp'
                    end
                end
            end
            if  v.compare then
                for kk, vv in pairs( v.compare ) do
                    if  type( vv )=='table' and vv.on then
                        FOO.server_config_ab_test[ k ].compare = kk
                        FOO.server_config_ab_id_list[ kk ] = vv.on
                        tmp_exp_config_[ k .. "_" .. kk ] = vv.on    --- exp_name999
                        FOO.ab_id_to_key[ kk ] = k
                        FOO.ab_id_to_type[ kk ] = 'compare'
                    end
                end
            end
        end
    end
    log.day_list_simple( "sdk_abtest","server_config_ab_test", uu.to_str(FOO.server_config_ab_test) )
    log.day_list_simple( "sdk_abtest","server_config_ab_id_list", uu.to_str(FOO.server_config_ab_id_list) )
    return tmp_exp_config_
end

--计算分桶hash结果
function FOO._CalMurMurHash3( uin_, domainID_, sceneID_, layerID_ )
    local gen_key = tostring( uin_ )..domainID_..sceneID_..layerID_
    local result = murmurhash3( gen_key, FOO.speed )
    result = result % 100
    return result
end

--检查一个配置数据是否有效(例如时间过期等)
function FOO._CheckTestData( sec_ )
    if sec_.trafic_expire > uu.now() then
        return true
    end
    return false
end

--判断是否能命中一个实验配置
function FOO._CheckIsHitData( sec_, uin_, domainID_, sceneID_, layerID_ )
    local blank_key = ''..domainID_ ..sceneID_..layerID_..uin_
    --取出当前层白名单的id
    local blank_layer_id = FOO.blank_guid_config[ blank_key ]
    if blank_layer_id ~= nil then
        if blank_layer_id == sec_.exp_id then
            return true
        else
            --这里直接返回，防止同层命中多个实验
            return false
        end
    end

    --判断是否能中桶
    local murMurHash3Val = FOO._CalMurMurHash3( uin_, domainID_, sceneID_, layerID_ )
    --log.day_list_simple( "ab_test", "CalMurMurHash3:"..murMurHash3Val, "uin:"..uin_,
    --        "domainID:"..domainID_, "sceneID:"..sceneID_, "layerID:"..layerID_ )   --查bug的时候打开日志
    for _, t_interval in ipairs( sec_.trafic_interval ) do
        local min = t_interval.min or 0
        local max = t_interval.max or 0
        if min <= murMurHash3Val and murMurHash3Val <= max then
            return true
        end
    end
    return false
end


--计算当前命中的实验, 使用角色uin
function FOO.CalcHitTest( uin_, exp_id_ )
    if  type( exp_id_ ) ~= "number" then
        log.debug( "exp_id is not number" )
        return
    end
    uin_    = tostring( uin_ )
    exp_id_ = tonumber(exp_id_)
    if  FOO.ab_test_config[ exp_id_ ] then
        local sec = FOO.ab_test_config[ exp_id_ ]
        if env.debug == 1 or env.debug == 11 then
            log.day_list_simple( "ab_test", "CalcHitTest", exp_id_, uin_, sec.trafic_domain.id, sec.trafic_scene.id, sec.logic_layer.id )
        end
        if  FOO._CheckTestData( sec ) and FOO._CheckIsHitData( sec, uin_,
            sec.trafic_domain.id, sec.trafic_scene.id, sec.logic_layer.id ) then
            return sec
        end
    else
        return
    end
end


--计算当前命中的实验，使用设备ID
function FOO.CalcHitTestDevID( p_device_id_, exp_id_ )
    if type( exp_id_ ) ~= "number" then
        log.debug( "exp_id is not number" )
    end
    p_device_id_ = tostring( p_device_id_ )
    exp_id_ = tonumber(exp_id_)
    if FOO.ab_test_config[ exp_id_ ] then
        local sec = FOO.ab_test_config[ exp_id_ ]
        if  FOO._CheckTestData( sec ) and FOO._CheckIsHitData( sec, p_device_id_,
                sec.trafic_domain.id, sec.trafic_scene.id, sec.logic_layer.id ) then
            return sec
        end
    else
        return nil
    end
end

--取出所有符合uin的实验
function FOO.CalcHitAllTest( p_uin_ )
    local uin_ = tostring(p_uin_)
    local hit_list_ = {}
    if env.debug == 1 or env.debug == 11 then
        log.day_list_simple( "ab_test", "CalcHitAllTest:sys_config_test_list",
                uin_, uu.to_str( FOO.server_config_ab_id_list ) )
    end
    for exp_id, _ in pairs( FOO.server_config_ab_id_list ) do
        --if v == 1 then
        local sec = FOO.ab_test_config[ exp_id ]
        if sec and FOO.CalcHitTest( uin_, exp_id ) then
            table.insert( hit_list_, sec.exp_id )
        end
        --end
    end
    if env.debug == 1 or env.debug == 11 then
        log.day_list_simple( "ab_test", "CalcHitAllTest:hit_list_", uin_, uu.to_str( hit_list_ ) )
    end
    return hit_list_
end

--取出所有符合uin的实验
function FOO.CalcHitAllTest2( p_uin_ )
    local uin_ = tostring(p_uin_)
    local hit_list_ = {}
    if env.debug == 1 or env.debug == 11 then
        log.day_list_simple( "ab_test", "CalcHitAllTest2:sys_config_test_list",
                uin_, uu.to_str( FOO.server_config_ab_id_list ) )
    end
    for exp_id, _ in pairs( FOO.server_config_ab_id_list ) do
        local sec = FOO.ab_test_config[ exp_id ]
        if sec and FOO.CalcHitTest( uin_, exp_id ) then
            table.insert( hit_list_, { id = sec.exp_id, key = FOO.ab_id_to_key[ sec.exp_id ], type = FOO.ab_id_to_type[ sec.exp_id ] } )
        end
    end
    if env.debug == 1 or env.debug == 11 then
        log.day_list_simple( "ab_test", "CalcHitAllTest2:hit_list_", uin_, uu.to_str( hit_list_ ) )
    end
    return hit_list_
end

---返回中桶的实验id 和 效果id
---configKey_:即服务器配置servers_config里面的ab_test_config->key
function FOO.getABTestExp( configKey_,  uin_ )
    log.debug( "getABTestExp:" .. uu.to_str(configKey_) .. ':' .. uu.to_str(uin_) )

    if FOO.server_config_ab_test[ configKey_] == nil then
        return nil, nil
    end
    --domainID_, sceneID_, layerID_
    local exp_id, compare_id
    exp_id     = FOO.server_config_ab_test[ configKey_].exp
    compare_id = FOO.server_config_ab_test[ configKey_].compare

    if FOO.ab_test_tj[ configKey_ ] == nil then
        FOO.ab_test_tj[ configKey_ ] = { call=0 }
    end
    --- 1 79 81
    local apiid_ = tonumber( ngx.ctx.m_params.apiid or 0 ) or 0

    ---打印统计信息
    FOO.ab_test_tj[ configKey_ ].call =  FOO.ab_test_tj[ configKey_ ].call + 1
    if  FOO.ab_test_tj[ configKey_ ].call >= AB_TEST_TJ_RATE then
        log.day_list_simple( "ab_test", "tj", configKey_, uu.table2json(FOO.ab_test_tj[ configKey_ ]) )
        FOO.ab_test_tj[ configKey_ ] = { call=0 }
    end

    local hitTestDatas = FOO.CalcHitTest( uin_, exp_id )
    if  (not hitTestDatas) and compare_id then
        hitTestDatas = FOO.CalcHitTest( uin_, compare_id )
    end

    if  hitTestDatas and hitTestDatas.exp_id then
        local domainID_, sceneID_, layerID = hitTestDatas.trafic_domain.id,
                                             hitTestDatas.trafic_scene.id,
                                             hitTestDatas.logic_layer.id
        local id_ = hitTestDatas.exp_id
        FOO.ab_test_tj[ configKey_ ][ id_ ] = (FOO.ab_test_tj[ configKey_ ][id_] or 0) + 1   --tj + 1

        ---判断 ab_test_config 实验配置
        if  FOO.server_config_ab_id_list[ id_ ] then
            local open_ = FOO.server_config_ab_id_list[ id_ ]
            log.day_list_simple( "ab_test", "hit", uin_, domainID_, sceneID_, layerID, id_, configKey_,
                    open_, ngx.ctx.m_params.uin, apiid_ )
            if  open_ == 1 then
                return id_, 1       --- 使用AI推荐服务器返回
            else
                return id_, 0       --- 使用旧功能返回
            end
        else
            log.day_list_simple( "ab_test", "hit", uin_, domainID_, sceneID_, layerID, id_,  configKey_,
                    "error_config", 0, ngx.ctx.m_params.uin, apiid_ )
        end

    elseif env.debug == 1 or env.debug == 11 then
        ---测试环境打印未中桶日志
        log.day_list_simple( "ab_test", "hit", uin_, 0, configKey_, "un_hit", 0, ngx.ctx.m_params.uin, apiid_ )
    end
    return nil, nil

end


return FOO